package org.refresh.core;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.refresh.core.expr.BinaryExpression;
import org.refresh.core.expr.ConstantIntegerExpression;
import org.refresh.core.expr.Expression;
import org.refresh.core.expr.SourceVariableSumExpression;
import org.refresh.core.expr.SourceVariableValueExpression;
import org.refresh.core.expr.TargetVariableValueExpression;
import org.refresh.core.expr.UnaryExpression;
import org.refresh.core.expr.VariableSumExpression;
import org.refresh.core.expr.VariableValueExpression;
import com.sun.corba.se.spi.legacy.connection.GetEndPointInfoAgainException;

import choco.Constraint;
import choco.ContradictionException;
import choco.Problem;
import choco.Solver;
import choco.Var;
import choco.integer.IntDomainVar;
import choco.integer.IntExp;
import choco.integer.IntVar;
import choco.integer.search.AssignVar;
import choco.integer.search.DecreasingDomain;
import choco.integer.search.IncreasingDomain;
import choco.integer.search.MinDomain;
import choco.integer.search.StaticVarOrder;
import choco.real.RealExp;
import choco.real.RealVar;

/*******************************************************************************
 * Copyright (c) 2005, 2006 Jules White. All rights reserved. This program and
 * the accompanying materials are made available under the terms of the Eclipse
 * Public License v1.0 which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors: Jules White - initial API and implementation
 ******************************************************************************/
public abstract class AbstractRefreshChocoCore implements ChocoRefreshCore {

	public class UndefinedAttributeException extends RuntimeException {

		public UndefinedAttributeException(String arg0) {
			super(arg0);
			// TODO Auto-generated constructor stub
		}

	}

	public class UnsupportedOperationException extends RuntimeException {

		public UnsupportedOperationException(String arg0) {
			super(arg0);
		}

	}

	public class UnsupportedExpressionType extends RuntimeException {

		public UnsupportedExpressionType(String arg0) {
			super(arg0);
		}

	}

	private boolean solved_ = false;

	protected Problem problem_;

	protected List sourceItems_ = new ArrayList();

	protected List targetItems_ = new ArrayList();

	protected Map<Object, Var> variableTable_ = new HashMap<Object, Var>();

	protected Map<Object, Map<Object, Var>> targetVariableTable_ = new HashMap<Object, Map<Object, Var>>();

	protected Map<Object, Map<Object, Var>> sourcetVariableTable_ = new HashMap<Object, Map<Object, Var>>();

	protected Map<Object, Map<Object, Object>> sourceVariableValuesTable_ = new HashMap<Object, Map<Object, Object>>();

	protected Map<Object, Map<Object, Object>> targetVariableValuesTable_ = new HashMap<Object, Map<Object, Object>>();

	private boolean zeroUndefinedVariables_ = true;

	private boolean useBranchAndBound_ = false;

	private IntExp optimizationFunction_;

	private boolean maximizeOptimizationFunction_ = true;

	/**
	 * @turn the zeroUndefinedVariables
	 */
	public boolean getZeroUndefinedVariables() {
		return zeroUndefinedVariables_;
	}

	/**
	 * @param zeroUndefinedVariables
	 *            the zeroUndefinedVariables to set
	 */
	public void setZeroUndefinedVariables(boolean zeroUndefinedVariables) {
		zeroUndefinedVariables_ = zeroUndefinedVariables;
	}

	/**
	 * @return the maximizeOptimizationFunction
	 */
	public boolean getMaximizeOptimizationFunction() {
		return maximizeOptimizationFunction_;
	}

	/**
	 * @param maximizeOptimizationFunction
	 *            the maximizeOptimizationFunction to set
	 */
	public void setMaximizeOptimizationFunction(
			boolean maximizeOptimizationFunction) {
		maximizeOptimizationFunction_ = maximizeOptimizationFunction;
	}

	/**
	 * @return the optimizationFunction
	 */
	public IntExp getOptimizationFunction() {
		return optimizationFunction_;
	}

	/**
	 * @param optimizationFunction
	 *            the optimizationFunction to set
	 */
	public void setOptimizationFunction(Expression exp) {
		optimizationFunction_ = getIntExpr(null, null, exp);
	}

	/**
	 * @return the useBranchAndBound
	 */
	public boolean getUseBranchAndBound() {
		return useBranchAndBound_;
	}

	/**
	 * @param useBranchAndBound
	 *            the useBranchAndBound to set
	 */
	public void setUseBranchAndBound(boolean useBranchAndBound) {
		useBranchAndBound_ = useBranchAndBound;
	}

	public void setSetsToMap(List srcs, List trgs) {
		problem_ = new Problem();

		sourceItems_ = srcs;
		targetItems_ = trgs;
	}

	public void setSetsToMap(Object[] srcs, Object[] trgs) {
		setSetsToMap(Arrays.asList(srcs), Arrays.asList(trgs));
	}

	public void setTargetVariableValues(Object target, Map values) {
		targetVariableValuesTable_.put(target, values);
	}

	public void setTargetVariableValues(Object target, Object[] values) {
		Map valuemap = new HashMap();
		for (int i = 0; i < values.length; i += 2) {
			valuemap.put(values[i], values[i + 1]);
		}
		targetVariableValuesTable_.put(target, valuemap);
	}

	public void setSourceVariableValues(Object source, Map values) {
		sourceVariableValuesTable_.put(source, values);
	}

	public void setSourceVariableValues(Object source, Object[] values) {
		Map valuemap = new HashMap();
		for (int i = 0; i < values.length; i += 2) {
			valuemap.put(values[i], values[i + 1]);
		}
		sourceVariableValuesTable_.put(source, valuemap);
	}

	protected Solver getTimeLimitedSolver(int maxtime) {
		Solver solver = getSolver();
		solver.setTimeLimit(maxtime);
		solver.generateSearchSolver(problem_);
		return solver;
	}

	protected Solver getNodeLimitedSolver(int maxnodes) {
		Solver solver = getSolver();
		solver.setNodeLimit(maxnodes);
		solver.generateSearchSolver(problem_);
		return solver;
	}

	protected Solver getUnlimitedSolver() {
		Solver s = getSolver();
		s.generateSearchSolver(problem_);
		return s;
	}

	protected Solver getSolver() {
		Solver solver = problem_.getSolver();
		solver.setDoMaximize(maximizeOptimizationFunction_);
		IntVar v = problem_.makeBoundIntVar("opt__criteria", -100000, 100000);
		problem_.post(problem_.eq(v, optimizationFunction_));
		solver.setObjective(v);
		solver.setRestart(useBranchAndBound_);
		solver.setFirstSolution(false);

		return solver;
	}

	public Map<Object, List> nextMapping() {
		return nextMapping(getUnlimitedSolver());
	}

	public Map<Object, List> nextMappingWithinTimeLimit(int time) {
		return nextMapping(getTimeLimitedSolver(time));
	}

	public Map<Object, List> nextMappingWithinNodeLimit(int nodes) {
		return nextMapping(getNodeLimitedSolver(nodes));
	}

	public Map<Object, List> nextMapping(Solver s) {
		boolean foundsol = false;

		if (!solved_) {
			s.launch();
			if (s.isEncounteredLimit())
				foundsol = false;
			else
				foundsol = problem_.isFeasible();
		}
		// if (optimizationFunction_ != null) {
		// if (solved_) {
		// return null;
		// }
		//
		// solved_ = true;
		// IntVar v = problem_.makeBoundIntVar("opt__criteria", -100000,
		// 100000);
		// problem_.post(problem_.eq(v, optimizationFunction_));
		// if (maximizeOptimizationFunction_) {
		// foundsol = problem_.maximize(v, useBranchAndBound_);
		// } else {
		// foundsol = problem_.minimize(v, useBranchAndBound_);
		// }
		// } else {
		// if (!solved_) {
		// solved_ = true;
		// foundsol = problem_.solve();
		// } else {
		// foundsol = problem_.nextSolution();
		// }
		// }
		if (foundsol) {
			Map<Object, List> solution = new HashMap<Object, List>();
			for (Object src : sourceItems_) {
				List valid = getTargets(src);
				solution.put(src, valid);
			}
			return solution;
		} else {
			return null;
		}
	}

	public abstract List getTargets(Object src);

	
	public Map getTargetVariableValues(Object target) {
		Map vals = targetVariableValuesTable_.get(target);
		if (vals == null) {
			vals = new HashMap();
			targetVariableValuesTable_.put(target, vals);
		}
		return vals;
	}

	public Map getSourceVariableValues(Object source) {
		Map vals = sourceVariableValuesTable_.get(source);
		if (vals == null) {
			vals = new HashMap();
			sourceVariableValuesTable_.put(source, vals);
		}
		return vals;
	}

	
	public Constraint getSourceMappingConstraint(Object src, Object trg,
			IntVar mapvar, BinaryExpression expr) {
		Constraint c = problem_.eq(mapvar, 1);
		return problem_.implies(c, getConstraint(src, trg, expr
				.getLeftHandSide(), expr.getRightHandSide(), expr
				.getOperation()));
	}

	public Constraint getConstraint(Expression lhs, Expression rhs, String op) {

		if (op.equals("=")) {
			return problem_.eq(getIntExpr(lhs), getIntExpr(rhs));
		} else if (op.equals("!=")) {
			return problem_.neq(getIntExpr(lhs), getIntExpr(rhs));
		} else if (op.equals("<")) {
			return problem_.lt(getIntExpr(lhs), getIntExpr(rhs));
		} else if (op.equals("=<")) {
			return problem_.leq(getIntExpr(lhs), getIntExpr(rhs));
		} else if (op.equals(">")) {
			return problem_.gt(getIntExpr(lhs), getIntExpr(rhs));
		} else if (op.equals(">=")) {
			return problem_.geq(getIntExpr(lhs), getIntExpr(rhs));
		} else {
			throw new UnsupportedOperationException("The operation \"" + op
					+ "\" is not supported.");
		}
	}

	public Constraint getConstraint(Object src, Object trg, Expression lhs,
			Expression rhs, String op) {

		if (op.equals("=")) {
			return problem_.eq(getIntExpr(src, trg, lhs), getIntExpr(src, trg,
					rhs));
		} else if (op.equals("!=")) {
			return problem_.neq(getIntExpr(src, trg, lhs), getIntExpr(src, trg,
					rhs));
		} else if (op.equals("<")) {
			return problem_.lt(getIntExpr(src, trg, lhs), getIntExpr(src, trg,
					rhs));
		} else if (op.equals("=<")) {
			return problem_.leq(getIntExpr(src, trg, lhs), getIntExpr(src, trg,
					rhs));
		} else if (op.equals(">")) {
			return problem_.gt(getIntExpr(src, trg, lhs), getIntExpr(src, trg,
					rhs));
		} else if (op.equals(">=")) {
			return problem_.geq(getIntExpr(src, trg, lhs), getIntExpr(src, trg,
					rhs));
		} else {
			throw new UnsupportedOperationException("The operation \"" + op
					+ "\" is not supported.");
		}
	}

	public void setIntVariableValue(Object variable, int value) {
		IntVar var = problem_.makeConstantIntVar(value);
		variableTable_.put(variable, var);
	}

	public void createIntVariable(Object variable, String op, Expression exp) {
		IntVar var = problem_.makeBoundIntVar("" + variable, -1000, 1000);
		variableTable_.put(variable, var);
		problem_.post(problem_.eq(var, getIntExpr(exp)));
	}

	public IntExp getIntExpr(Object src, Object trg, Expression exp) {
		if (exp instanceof BinaryExpression) {
			BinaryExpression bexp = (BinaryExpression) exp;
			Expression lhs = bexp.getLeftHandSide();
			Expression rhs = bexp.getRightHandSide();
			String op = bexp.getOperation();
			return getIntExpr(getIntExpr(src, trg, lhs), getIntExpr(src, trg,
					rhs), op);
		} else {
			if (exp instanceof VariableValueExpression) {
				Object var = ((VariableValueExpression) exp).getVariable();
				if (exp instanceof SourceVariableValueExpression) {
					return getOrCreateIntVariableValue(src, var,
							sourcetVariableTable_, sourceVariableValuesTable_);
				} else if (exp instanceof TargetVariableValueExpression) {
					return getOrCreateIntVariableValue(trg, var,
							targetVariableTable_, targetVariableValuesTable_);
				} else {
					return (IntExp) variableTable_
							.get(((VariableValueExpression) exp).getVariable());
				}
			} else if (exp instanceof VariableSumExpression) {
				Object var = ((VariableSumExpression) exp).getVariable();
				if (exp instanceof SourceVariableSumExpression) {
					return getOrCreateSummation("src_", var,
							sourcetVariableTable_, sourceVariableValuesTable_);
				} else {
					return getOrCreateSummation("trg_", var,
							targetVariableTable_, targetVariableValuesTable_);
				}
			} else if (exp instanceof ConstantIntegerExpression) {
				return problem_
						.makeConstantIntVar(((ConstantIntegerExpression) exp)
								.getValue());
			} else {
				throw new UnsupportedExpressionType("The \"" + exp
						+ "\" is not a known expression type.");
			}
		}
	}

	public IntExp getOrCreateSummation(Object key, Object var,
			Map<Object, Map<Object, Var>> avals,
			Map<Object, Map<Object, Object>> vals) {
		IntDomainVar idv = (IntDomainVar) variableTable_.get(key + SUM_PREFIX
				+ var);

		if (idv == null) {
			int count = 0;
			IntExp[] valvars = new IntExp[vals.keySet().size()];
			for (Object item : vals.keySet()) {
				IntVar pvar = getOrCreatePresenceVariable(item);
				int dval = getValue(item, var, vals);

				IntVar dvar = problem_.makeBoundIntVar(key + "demand_of_"
						+ item + "_" + var, 0, dval);
				problem_.post(problem_.eq(dvar, problem_.mult(dval, pvar)));
				valvars[count] = dvar;
				count++;
			}
			idv = problem_.makeBoundIntVar(key + SUM_PREFIX + var, -100000,
					100000);
			problem_.post(problem_.eq(idv, problem_.sum(valvars)));
			variableTable_.put(key + SUM_PREFIX + var, idv);
		}
		return idv;
	}

	public int getValue(Object item, Object var,
			Map<Object, Map<Object, Object>> vals) {

		Map valtable = vals.get(item);
		if (valtable == null) {
			valtable = new HashMap();
			vals.put(item, valtable);
		}
		Object val = valtable.get(var);
		if (val == null && !zeroUndefinedVariables_) {
			throw new UndefinedAttributeException("The attribute \"" + var
					+ "\" has not been defined for the item:\"" + item + "\".");
		}
		int ival = (val != null) ? ((Integer) val).intValue() : 0;
		return ival;
	}

	public abstract IntVar getOrCreatePresenceVariable(Object item);

	public IntVar getOrCreateIntVariableValue(Object item, Object var,
			Map<Object, Map<Object, Var>> avals,
			Map<Object, Map<Object, Object>> vals) {
		Map valvtable = avals.get(item);
		if (valvtable == null) {
			valvtable = new HashMap();
			avals.put(item, valvtable);
		}
		IntVar v = (IntVar) valvtable.get(var);
		if (v == null) {

			int ival = getValue(item, var, vals);
			v = problem_.makeConstantIntVar(ival);
			avals.get(item).put(var, v);
		}
		return v;
	}

	public IntExp getIntExpr(Expression exp) {
		if (exp instanceof BinaryExpression) {
			BinaryExpression bexp = (BinaryExpression) exp;
			Expression lhs = bexp.getLeftHandSide();
			Expression rhs = bexp.getRightHandSide();
			String op = bexp.getOperation();
			return getIntExpr(getIntExpr(lhs), getIntExpr(rhs), op);
		} else {
			if (exp instanceof VariableValueExpression) {
				return (IntExp) variableTable_
						.get(((VariableValueExpression) exp).getVariable());
			} else if (exp instanceof ConstantIntegerExpression) {
				return problem_
						.makeConstantIntVar(((ConstantIntegerExpression) exp)
								.getValue());
			} else {
				throw new UnsupportedExpressionType("The \"" + exp
						+ "\" is not a known expression type.");
			}
		}
	}

	public IntExp getIntExpr(IntExp lhs, IntExp rhs, String op) {
		if (op.equals("+")) {
			return problem_.plus(lhs, rhs);
		} else if (op.equals("-")) {
			return problem_.minus(lhs, rhs);
		} else {
			throw new UnsupportedOperationException("The operation \"" + op
					+ "\" is not supported.");
		}
	}

	public abstract void setValidTargets(Object src, List valid) ;

	public abstract void setInValidTargets(Object src, List invalid);
	
	public abstract void addSourceMappedCardinalityConstraint(Object src, int min,
			int max);

	public abstract void addTargetMappedSourcesCardinalityConstraint(Object target,
			int min, int max);
	
	public abstract void addTargetMappedSourcesConstraint(Object target, int exactmapped) ;

	public abstract void addTargetIntResourceConstraint(Object target, Object var,
			int val) ;

	public abstract void addSourceMappingConstraint(Object src,
			BinaryExpression expression) ;
	
	public abstract void addRequiresMappingConstraint(Object src, Object req);
	
	public abstract void addExcludesMappingConstraint(Object src, Object req) ;
		
	public abstract void addSelectMappingConstraint(Object src, List set, int min,
			int max) ;
	
	public abstract void addSelectMappingConstraint(Object src, List set, int exactcard);
	
	public void requireAllMapped(){
		for(Object src : sourceItems_){
			IntVar pres = getOrCreatePresenceVariable(src);
			problem_.post(problem_.geq(pres, 1));
		}
	}

	public abstract void requireAllMappedExactlyOnce();
}
